package lang;

import editor.NotImplementedException;
import model.Project;
import model.Segment;
import model.code.*;
import model.code.line.*;
import model.symbol.Constant;
import model.symbol.Scope;
import model.symbol.Symbol;
import observer.Copiable;
import observer.ObservedList;
import util.ByteList;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * This is the superclass of all language encoding classes.
 *
 * @author Neil Dickson
 */
public abstract class Encoder {
	public static final int		OUTPUT_TYPE_UNSPECIFIED	= -1;
	public static final int		OUTPUT_TYPE_WIN32_EXE	= 0;
	public static final int		OUTPUT_TYPE_WIN64_EXE	= 1;
	public static final int		OUTPUT_TYPE_WIN32_DLL	= 2;
	public static final int		OUTPUT_TYPE_WIN64_DLL	= 3;
	public static final int		OUTPUT_TYPE_OS_IMAGE	= 4;
	public static final int		NUM_OUTPUT_TYPES		= 5;

	private static final short	SIZEOF_DOS_STUB			= 0xB8;
	private static final short	SIZEOF_COFF_HEADER		= 0x18;
	private static final int	OFFSET_PE_HEADER		= SIZEOF_DOS_STUB+SIZEOF_COFF_HEADER;
	private static final short	SIZEOF_PE32_HEADER		= 224;//224-24;	// NOTE: The last 3 RVA/size pair entries aren't needed, saving 24 bytes.
	private static final short	SIZEOF_PE32PLUS_HEADER	= 240;//240-24;	//
	private static final int	NUM_RVA_TABLE_ENTRIES	= 16;			//
	private static final int	OFFSET_RVA_TABLE32		= SIZEOF_DOS_STUB+SIZEOF_COFF_HEADER+SIZEOF_PE32_HEADER-(NUM_RVA_TABLE_ENTRIES*8);
	private static final int	OFFSET_RVA_TABLE64		= SIZEOF_DOS_STUB+SIZEOF_COFF_HEADER+SIZEOF_PE32PLUS_HEADER-(NUM_RVA_TABLE_ENTRIES*8);
	private static final int	RVA_INDEX_EXPORTS		= 0;
	private static final int	RVA_INDEX_IDT			= 1;
	private static final int	RVA_INDEX_RELOCATIONS	= 5;
	private static final int	RVA_INDEX_IAT			= 12;
	private static final int	OFFSET_SECTION_TABLE32	= OFFSET_RVA_TABLE32+NUM_RVA_TABLE_ENTRIES*8;
	private static final int	OFFSET_SECTION_TABLE64	= OFFSET_RVA_TABLE64+NUM_RVA_TABLE_ENTRIES*8;
	private static final int	SIZEOF_SECTION_ENTRY	= 0x28;
	private static final int	SECTION_INDEX_CODE		= 0;
	private static final int	SECTION_INDEX_RDATA		= 1;
	private static final int	SECTION_INDEX_DATA		= 2;
	private static final int	NUM_SECTIONS			= 3;
	private static final int	HEADER_SIZE32			= OFFSET_SECTION_TABLE32+NUM_SECTIONS*SIZEOF_SECTION_ENTRY;
	private static final int	HEADER_SIZE64			= OFFSET_SECTION_TABLE64+NUM_SECTIONS*SIZEOF_SECTION_ENTRY;
	private static final short	PE32_MARKER				= 0x10B;
	private static final short	PE32PLUS_MARKER			= 0x20B;
	private static final short	HEADER_FLAG_FIXED		= 0x0001;
	private static final short	HEADER_FLAG_EXECUTABLE	= 0x0002;
	private static final short	HEADER_FLAG_LARGE_ADDR	= 0x0020;
	private static final short	HEADER_FLAG_LIBRARY		= 0x2000;
	public static final int		FILE_ALIGNMENT			= 0x200;	// 512-byte sector size
	public static final int		SECTION_ALIGNMENT		= 0x1000;	// 4KB page size; 0x200000 for 2MB page size
	private static final long	IMAGE_BASE_EXE			= 0x400000;
	private static final long	IMAGE_BASE_DLL			= 0x10000000;

	private static final int SECTION_FLAG_CONTAINS_CODE		= 0x20;
	private static final int SECTION_FLAG_CONTAINS_INIT_DATA= 0x40;
	private static final int SECTION_FLAG_EXECUTABLE		= 0x20000000;
	private static final int SECTION_FLAG_READABLE			= 0x40000000;
	private static final int SECTION_FLAG_WRITABLE			= 0x80000000;

	/**
	 * An absolute 2/4-byte relocation (remains a relocation in an executable).
	 */
	public static final int		RELOC_NORMAL	= 0;
	/**
	 * A relative 2/4-byte relocation (relative to RIP).
	 */
	public static final int		RELOC_REL		= 1;
	/**
	 * A 2/4-byte relocation that's the difference between two addresses.
	 * (Assumed to be 2/4-byte even if could be done in 1 byte, for simplicity.)
	 */
	public static final int		RELOC_DIFF	= 2;
	/**
	 * If exceeds range (-128 to +127), turns into RELOC_REL with 2/4-byte relocation,
	 * 0Fh is inserted before previous byte, and 10h is added to previous byte (7x to 0F 8x).
	 */
	public static final int		RELOC_JCC_SHORT	= 3;
	/**
	 * If exceeds range (-128 to +127), turns into RELOC_REL with 2/4-byte relocation
	 * and 1 is added to previous byte (E8 to E9).
	 */
	public static final int		RELOC_JMP_SHORT	= 4;
	/**
	 * If exceeds range (-128 to +127), an error occurs, because there is no longer encoding.
	 */
	public static final int		RELOC_JMP_SHORT_ONLY	= 5;

	/**
	 * Encodes and links the given project as a series of {@link Segment}s, and fills in errors if any are present.
	 * @param project the project to encode
	 * @param errors an empty list to receive any errors that occur in compilation
	 * @return the name of the encoded file, or null if fatal errors occur
	 * @throws Throwable if something goes wrong
	 */
	public static String encodeProject(final Project project,final ArrayList<SyntaxError> errors) throws Throwable {
		// Gather a list of all functions and variables to be assembled
		ArrayList<CodeFile> files = project.getDirectory().getAllFiles();
		final ArrayList<CodeFileChunk> functionsAndVars = new ArrayList<CodeFileChunk>(files.size()*32);
		for (CodeFile file : files) {
			ObservedList<CodeFileChunk> content = file.getContent();
			for (CodeFileChunk chunk : content) {
				if (chunk instanceof Function || chunk instanceof GlobalVariable) {
					functionsAndVars.add(chunk);
				}
				else if (chunk instanceof Section) {
					ObservedList<CodeFileChunk> sectionContent = ((Section)chunk).getContent();
					for (CodeFileChunk c : sectionContent) {
						if (c instanceof Function || c instanceof GlobalVariable) {
							functionsAndVars.add(c);
						}
					}
				}
			}
		}
		files = null;	// Set files to null so that it can be garbage-collected
		functionsAndVars.trimToSize();

		return encodeProject(project,project.getEntryPoint(),functionsAndVars,null,errors);
	}
	public static String encodeProject(final Project project,final Function entryPoint,final String executableName,final ArrayList<SyntaxError> errors) throws Throwable {
		// Gather a list of all functions and variables to be assembled
		HashMap<Long,CodeFileChunk> chunksToEncode = new HashMap<Long,CodeFileChunk>(256);
		chunksToEncode.put(entryPoint.getID(),entryPoint);
		ArrayList<CodeFileChunk> toBeVisited = new ArrayList<CodeFileChunk>(128);
		ArrayList<CodeFileChunk> functionsAndVars = new ArrayList<CodeFileChunk>(256);
		toBeVisited.add(entryPoint);
		while (!toBeVisited.isEmpty()) {
			CodeFileChunk chunk = toBeVisited.remove(toBeVisited.size()-1);
			functionsAndVars.add(chunk);
			if (chunk instanceof Function) {
				// TODO: Handle preprocessor exclusion so that things don't get included unnecessarily.
				MultiLineList lines = ((Function)chunk).getCode();
				for (Line line : lines) {
					findGlobalReferences(project,line,chunksToEncode,toBeVisited);
				}
			}
			else {	// chunk instanceof GlobalVariable
				ObservedList<GlobalVariable.Piece> pieces = ((GlobalVariable)chunk).getPieces();
				for (GlobalVariable.Piece piece : pieces) {
					findGlobalReferences(project,piece.getInitialValue(),chunksToEncode,toBeVisited);
				}
			}
		}
		chunksToEncode = null;
		toBeVisited = null;
		functionsAndVars.trimToSize();

		// TODO: Order them within the files that contain them to maintain proper order for explicit offsets, etc.

		return encodeProject(project,entryPoint,functionsAndVars,executableName,errors);
	}
	private static void findGlobalReferences(final Project project,final Line line,final HashMap<Long,CodeFileChunk> chunksToEncode,final ArrayList<CodeFileChunk> toBeVisited) {
		Object[] refs = line.getReferences();
		for (int i=0;i<line.getNumReferences();++i) {
			if (((refs[i] instanceof Function && !((Function)refs[i]).isImported()) || refs[i] instanceof GlobalVariable) && !chunksToEncode.containsKey(((Symbol)refs[i]).getID())) {
				chunksToEncode.put(((Symbol)refs[i]).getID(),(CodeFileChunk)refs[i]);
				toBeVisited.add((CodeFileChunk)refs[i]);
			}
			else if (refs[i] instanceof Constant) {
				Immediate value = ((Constant)refs[i]).getImmediate();
				if (value!=null) {
					if (value.relocation!=null && project.getScope().contains(value.relocation.symbolID) && !chunksToEncode.containsKey(value.relocation.symbolID)) {
						CodeFileChunk globalSymbol = (CodeFileChunk)project.getScope().get(value.relocation.symbolID);
						if (!(globalSymbol instanceof Function && ((Function)globalSymbol).isImported())) {
							chunksToEncode.put(value.relocation.symbolID,globalSymbol);
							toBeVisited.add(globalSymbol);
						}
					}
					if (value.diffRelocation!=null) {
						if (project.getScope().contains(value.diffRelocation.symbolID) && !chunksToEncode.containsKey(value.diffRelocation.symbolID)) {
							CodeFileChunk globalSymbol = (CodeFileChunk)project.getScope().get(value.diffRelocation.symbolID);
							if (!(globalSymbol instanceof Function && ((Function)globalSymbol).isImported())) {
								chunksToEncode.put(value.diffRelocation.symbolID,globalSymbol);
								toBeVisited.add(globalSymbol);
							}
						}
						if (project.getScope().contains(value.diffRelocation.negativeSymbolID) && !chunksToEncode.containsKey(value.diffRelocation.negativeSymbolID)) {
							CodeFileChunk globalSymbol = (CodeFileChunk)project.getScope().get(value.diffRelocation.negativeSymbolID);
							if (!(globalSymbol instanceof Function && ((Function)globalSymbol).isImported())) {
								chunksToEncode.put(value.diffRelocation.negativeSymbolID,globalSymbol);
								toBeVisited.add(globalSymbol);
							}
						}
					}
				}
				else if (((Constant)refs[i]).getValue().getNumReferences()==1 && (((Constant)refs[i]).getValue().getReferences()[0] instanceof Function || ((Constant)refs[i]).getValue().getReferences()[0] instanceof GlobalVariable)) {
					CodeFileChunk globalSymbol = (CodeFileChunk)((Constant)refs[i]).getValue().getReferences()[0];
					if (!(globalSymbol instanceof Function && ((Function)globalSymbol).isImported())) {
						chunksToEncode.put(globalSymbol.getID(),globalSymbol);
						toBeVisited.add(globalSymbol);
					}
				}
			}
		}
	}
	private static String encodeProject(final Project project,final Function entryPoint,final ArrayList<CodeFileChunk> functionsAndVars,String executableName,final ArrayList<SyntaxError> errors) throws Throwable {
		final ArrayList<Segment> segments = project.getSegments();
		
		// Determine info about the output format
		int outputType = project.getOutputType().get();
		if (outputType==OUTPUT_TYPE_UNSPECIFIED) {
			boolean has64BitSegment = false;
			boolean has32BitSegment = false;
			boolean has16BitSegment = false;
			for (Segment segment : segments) {
				if (segment.getnBits().get()==64) {
					has64BitSegment = true;
				}
				else if (segment.getnBits().get()==32) {
					has32BitSegment = true;
				}
				else { // segment.getnBits().get()==16
					has16BitSegment = true;
				}
			}
			boolean isMixed = (has64BitSegment&&has32BitSegment) || (has64BitSegment&&has16BitSegment) || (has32BitSegment&&has16BitSegment);
			if (has64BitSegment && !isMixed) {
				outputType = OUTPUT_TYPE_WIN64_EXE;
			}
			else if (has32BitSegment && !isMixed) {
				outputType = OUTPUT_TYPE_WIN32_EXE;
			}
			else {
				outputType = OUTPUT_TYPE_OS_IMAGE;
			}
		}
		final boolean is64Bit = outputType==OUTPUT_TYPE_WIN64_EXE || outputType==OUTPUT_TYPE_WIN64_DLL;
		final boolean isWindows = is64Bit || outputType==OUTPUT_TYPE_WIN32_EXE || outputType==OUTPUT_TYPE_WIN32_DLL;
		final boolean isDLL = outputType==OUTPUT_TYPE_WIN64_DLL || outputType==OUTPUT_TYPE_WIN32_DLL;
		final int headerSegmentCount = isWindows ? 1 : 0;

		// Assemble each Function and GlobalVariable independently
		final HashMap<Long,Function> importSet = new HashMap<Long,Function>();
		final int numProcessors = Runtime.getRuntime().availableProcessors();
		final EncodedData[] encodings = new EncodedData[functionsAndVars.size()+headerSegmentCount];
		final AtomicInteger nextToEncode = new AtomicInteger(0);
		final Throwable[] exceptionRef = new Throwable[]{null};
		// and put the indices of all functions and global variables into a HashMap indexed by ID so that their info can be found later
		final HashMap<Long,Integer> globalSymbolIndices = new HashMap<Long,Integer>(encodings.length);
		final CyclicBarrier barrier = new CyclicBarrier(numProcessors+1);
		for (int i=0;i<numProcessors;++i) {
			Thread thread = new Thread() {
				public void run() {
					try {
						int i;
						while ((i = nextToEncode.getAndIncrement())<functionsAndVars.size()) {
							CodeFileChunk chunk = functionsAndVars.get(i);
							Encoder encoder = chunk.getLanguage().getEncoder();
							if (chunk instanceof Function) {
								encodings[i] = encoder.encode((Function)chunk,importSet,errors);
								if (encodings[i]!=null && encodings[i].encoded!=null) {
									resolveJumps(encodings[i],chunk.getMode(),errors);
									encodings[i].size = encodings[i].encoded.size();
								}
							}
							else {	// chunk instanceof GlobalVariable
								encodings[i] = encoder.encode((GlobalVariable)chunk,errors);
							}
							synchronized (globalSymbolIndices) {
								globalSymbolIndices.put(chunk.getID(),i);
							}
						}
						// If there's a file header, have the first thread to finish create the header
						if (headerSegmentCount==1 && i==functionsAndVars.size()) {
							if (isWindows) {
								encodings[i] = createPEHeader(entryPoint,is64Bit,isDLL);
							}
							else {
								throw new NotImplementedException("File headers for non-windows output formats aren't supported, but somehow the header count is 1.");
							}
						}
					}
					catch (Throwable t) {
						// If an exception occurs, save it, and finish off the rest of the threads
						exceptionRef[0] = t;
						nextToEncode.set(encodings.length);
					}
					try {
						barrier.await();
					}
					catch (Exception e) {
						e.printStackTrace();
					}
				}
			};
			thread.start();
		}
		barrier.await();
		if (exceptionRef[0]!=null) {
			throw exceptionRef[0];
		}
		if (!errors.isEmpty()) {
			return null;
		}

		EncodedData[] newEncodings = encodings;
		final EncodedData headerEncoding = (headerSegmentCount==1) ? encodings[functionsAndVars.size()] : null;

		// Handle imports/exports in the default read-only data segment in a separate thread
		if (isWindows && !importSet.isEmpty()) {
			HashMap<String,ArrayList<Function>> libraries = new HashMap<String,ArrayList<Function>>();
			// Arrange the functions by library, sorted by name.
			for (Function function : importSet.values()) {
				String library = function.getFile().getName().toString();
				library = library.substring(0,library.length()-Loader.LIBRARY_INCLUDE_EXTENSION.length())+".dll";
				ArrayList<Function> functions = libraries.get(library);
				if (functions==null) {
					functions = new ArrayList<Function>();
					libraries.put(library,functions);
					functions.add(function);
				}
				else {
					String name = function.getName().toString();
					int j=0;
					while (j<functions.size() && functions.get(j).getName().compareTo(name)<0) {
						++j;
					}
					functions.add(j,function);
				}
			}

			int rdataSegmentIndex = segments.indexOf(project.getDefaultSegment(Segment.READ_ONLY_DATA));
			int addressSize = is64Bit ? 8 : 4;
			long executableLoadAddress = isDLL ? IMAGE_BASE_DLL : IMAGE_BASE_EXE;

			// The imports data consists of an Import Address Table (IAT), Import Directory Table (IDT), Import Lookup Table (ILT), and name strings
			// The IAT contains pointers that are initially identical to the ILT, but upon loading, point to the imported functions.
			// It contains one null entry after the entries for each library.
			EncodedData[] IAT = new EncodedData[importSet.size()+libraries.size()];
			// The IDT contains pointers to the beginning of the IAT and ILT entries for each library, as well as a pointer to the name of each library.
			// It contains one null entry at the end.
			EncodedData[] IDT = new EncodedData[libraries.size()+1];
			// The ILT contains pointers to the names of the corresponding imported functions, and the functions of each single library are sorted by name.
			// It contains one null entry after the entries for each library.
			EncodedData[] ILT = new EncodedData[importSet.size()+libraries.size()];
			// The name table contains ordinal-name pairs for each imported function, and the names of each library between the names for each library's functions.
			// The length of each name must be padded to an even number of bytes to keep everything at even alignment.
			EncodedData[] nameTable = new EncodedData[importSet.size()+libraries.size()];
			int index = 0;
			int libraryIndex = 0;
			long IDTID = Scope.getUniqueID();
			for (String library : libraries.keySet()) {
				ArrayList<Function> functions = libraries.get(library);
				long ILTID = Scope.getUniqueID();
				final long firstILTID = ILTID;
				final long firstIATID = functions.get(0).getID();
				for (Function function : functions) {
					// Figure out name entry first, because IAT and ILT refer to its address
					nameTable[index] = new EncodedData();
					nameTable[index].encoded = new ByteList(2+function.getName().length()+1);
					// Don't know the actual ordinal, so just guess zero
					nameTable[index].encoded.addShort((short)0);
					nameTable[index].encoded.addString(function.getName().toString());
					nameTable[index].encoded.add((byte)0);
					// Must be aligned to even offset
					nameTable[index].alignment = 2;
					// Create one SymbolOffset representing the offset of this ordinal-name entry
					long nameID = Scope.getUniqueID();
					nameTable[index].symbolOffsets = new ArrayList<SymbolOffset>(1);
					nameTable[index].symbolOffsets.add(new SymbolOffset(nameID,0));
					nameTable[index].segmentIndex = rdataSegmentIndex;
					nameTable[index].size = nameTable[index].encoded.size();
					globalSymbolIndices.put(nameID,encodings.length+IAT.length+IDT.length+ILT.length+index);

					IAT[index] = new EncodedData();
					ILT[index] = new EncodedData();
					IAT[index].encoded = new ByteList(addressSize);
					ILT[index].encoded = new ByteList(addressSize);
					// The references in the IAT and ILT are relative to the executable load address
					if (is64Bit) {
						// Relocations only modify up to 4 bytes, so only set up to 4 bytes as the negative number
						IAT[index].encoded.addLong((-executableLoadAddress)&0xFFFFFFFFL);
						ILT[index].encoded.addLong((-executableLoadAddress)&0xFFFFFFFFL);
					}
					else {
						IAT[index].encoded.addInt((int)-executableLoadAddress);
						ILT[index].encoded.addInt((int)-executableLoadAddress);
					}
					// Both IAT and ILT entries point to the ordinal-name entry
					IAT[index].relocations = new ArrayList<Relocation>(1);
					ILT[index].relocations = IAT[index].relocations;
					IAT[index].relocations.add(new Relocation(null,RELOC_NORMAL,0,nameID));
					// IAT entry is the function reference; i.e. the address of the function will be stored here, so treat it as the function.
					IAT[index].symbolOffsets = new ArrayList<SymbolOffset>(1);
					IAT[index].symbolOffsets.add(new SymbolOffset(function.getID(),0));
					globalSymbolIndices.put(function.getID(),encodings.length+index);
					// ILT entry only needs to define a symbol if it's the first ILT entry for this library
					if (ILTID>=0) {
						ILT[index].symbolOffsets = new ArrayList<SymbolOffset>(1);
						ILT[index].symbolOffsets.add(new SymbolOffset(ILTID,0));
						globalSymbolIndices.put(ILTID,encodings.length+IAT.length+IDT.length+index);
						ILTID = -1;
					}
					IAT[index].alignment = addressSize;
					IAT[index].segmentIndex = rdataSegmentIndex;
					ILT[index].segmentIndex = rdataSegmentIndex;
					IAT[index].size = IAT[index].encoded.size();
					ILT[index].size = ILT[index].encoded.size();

					++index;
				}
				nameTable[index] = new EncodedData();
				nameTable[index].encoded = new ByteList(library.length()+1);
				nameTable[index].encoded.addString(library);
				nameTable[index].encoded.add((byte)0);
				// Must be aligned to even offset
				nameTable[index].alignment = 2;
				// Create one SymbolOffset representing the offset of this library name entry
				long nameID = Scope.getUniqueID();
				nameTable[index].symbolOffsets = new ArrayList<SymbolOffset>(1);
				nameTable[index].symbolOffsets.add(new SymbolOffset(nameID,0));
				nameTable[index].segmentIndex = rdataSegmentIndex;
				nameTable[index].size = nameTable[index].encoded.size();
				globalSymbolIndices.put(nameID,encodings.length+IAT.length+IDT.length+ILT.length+index);

				// null entries for IAT and ILT; they contain no references and nothing refers to them
				IAT[index] = new EncodedData();
				ILT[index] = new EncodedData();
				IAT[index].encoded = new ByteList(addressSize);
				ILT[index].encoded = new ByteList(addressSize);
				if (is64Bit) {
					IAT[index].encoded.addLong(0);
					ILT[index].encoded.addLong(0);
				}
				else {
					IAT[index].encoded.addInt(0);
					ILT[index].encoded.addInt(0);
				}
				IAT[index].segmentIndex = rdataSegmentIndex;
				ILT[index].segmentIndex = rdataSegmentIndex;
				IAT[index].size = IAT[index].encoded.size();
				ILT[index].size = ILT[index].encoded.size();

				++index;

				// Define the library's IDT entry
				IDT[libraryIndex] = new EncodedData();
				IDT[libraryIndex].encoded = new ByteList(20);
				IDT[libraryIndex].encoded.addInt((int)-executableLoadAddress);	// offset into ILT
				IDT[libraryIndex].encoded.addLong(0);
				IDT[libraryIndex].encoded.addInt((int)-executableLoadAddress);	// offset of library name
				IDT[libraryIndex].encoded.addInt((int)-executableLoadAddress);	// offset into IAT
				// IDT entry points to the start of its ILT entries, its library's name, and the start of its IAT entries
				IDT[libraryIndex].relocations = new ArrayList<Relocation>(3);
				IDT[libraryIndex].relocations.add(new Relocation(null,RELOC_NORMAL,0,firstILTID));
				IDT[libraryIndex].relocations.add(new Relocation(null,RELOC_NORMAL,12,nameID));
				IDT[libraryIndex].relocations.add(new Relocation(null,RELOC_NORMAL,16,firstIATID));
				// IDT entry only needs to define a symbol if it's the first IDT entry
				if (IDTID>=0) {
					IDT[libraryIndex].symbolOffsets = new ArrayList<SymbolOffset>(1);
					IDT[libraryIndex].symbolOffsets.add(new SymbolOffset(IDTID,0));
					globalSymbolIndices.put(IDTID,encodings.length+IAT.length+libraryIndex);
					// File header also only needs IAT RVA and IDT RVA once
					int RVATableOffset = (is64Bit ? OFFSET_RVA_TABLE64 : OFFSET_RVA_TABLE32);
					int IDTRVAOffset = RVATableOffset+RVA_INDEX_IDT*8;
					int IATRVAOffset = RVATableOffset+RVA_INDEX_IAT*8;
					headerEncoding.relocations.add(new Relocation(null,RELOC_NORMAL,IDTRVAOffset,IDTID));
					headerEncoding.encoded.setInt(IDTRVAOffset,(int)-executableLoadAddress);
					headerEncoding.encoded.setInt(IDTRVAOffset+4,(libraries.size()+1)*20);
					headerEncoding.relocations.add(new Relocation(null,RELOC_NORMAL,IATRVAOffset,firstIATID));
					headerEncoding.encoded.setInt(IATRVAOffset,(int)-executableLoadAddress);
					headerEncoding.encoded.setInt(IATRVAOffset+4,(importSet.size()+libraries.size())*addressSize);
					IDTID = -1;
				}
				IDT[libraryIndex].segmentIndex = rdataSegmentIndex;
				IDT[libraryIndex].size = IDT[libraryIndex].encoded.size();
				++libraryIndex;
			}

			// Add the null IDT entry
			IDT[libraryIndex] = new EncodedData();
			IDT[libraryIndex].encoded = new ByteList(20);
			IDT[libraryIndex].encoded.addInt(0);
			IDT[libraryIndex].encoded.addLong(0);
			IDT[libraryIndex].encoded.addInt(0);
			IDT[libraryIndex].encoded.addInt(0);
			IDT[libraryIndex].segmentIndex = rdataSegmentIndex;
			IDT[libraryIndex].size = IDT[libraryIndex].encoded.size();
			++libraryIndex;

			newEncodings = new EncodedData[encodings.length+IAT.length+IDT.length+ILT.length+nameTable.length];
			System.arraycopy(encodings,0,newEncodings,0,encodings.length);
			System.arraycopy(IAT,0,newEncodings,encodings.length,IAT.length);
			System.arraycopy(IDT,0,newEncodings,encodings.length+IAT.length,IDT.length);
			System.arraycopy(ILT,0,newEncodings,encodings.length+IAT.length+IDT.length,ILT.length);
			System.arraycopy(nameTable,0,newEncodings,encodings.length+IAT.length+IDT.length+ILT.length,nameTable.length);
		}
		if (outputType==OUTPUT_TYPE_OS_IMAGE && !importSet.isEmpty()) {
			synchronized (errors) {
				errors.add(new SyntaxError(null,0,0,"Operating Systems can't import functions, and there are "+importSet.size()+" currently imported.  One is "+importSet.values().iterator().next().getName()+"."));
			}
			return null;
		}

		// Go linearly through each Segment to determine the offsets of everything and sizes of segments
		final long[] segmentSizes = new long[headerSegmentCount+segments.size()];
		for (int i=0;i<newEncodings.length;++i) {
			CodeFileChunk chunk = (i<functionsAndVars.size()) ? functionsAndVars.get(i) : null;
			EncodedData encoding = newEncodings[i];
			if (encoding!=null) {
				int segmentIndex = headerSegmentCount + encoding.segmentIndex;
				// First, figure out the offset of the function or variable
				if (encoding.offset>=0) {
					if (encoding.offset>= segmentSizes[segmentIndex]) {
						segmentSizes[segmentIndex] = encoding.offset;
					}
					else {
						ValueLine offsetLine = (chunk instanceof Function) ? ((Function)chunk).getOffset() : ((GlobalVariable)chunk).getOffset();
						SyntaxError error = new SyntaxError(offsetLine,0,offsetLine.length(),"This offset ("+Long.toHexString(encoding.offset)+"h) overlaps code or data that precedes it ("+Long.toHexString(segmentSizes[segmentIndex])+"h bytes)");
						offsetLine.addError(error);
						synchronized (errors) {
							errors.add(error);
						}
					}
				}
				else if (encoding.alignment!=CodeFileChunk.DEFAULT_ALIGNMENT_MARKER && encoding.alignment>=2) {
					segmentSizes[segmentIndex] = (segmentSizes[segmentIndex]+encoding.alignment-1)&(~(encoding.alignment-1));
				}
				// Save the offset so that it can be stored there later
				encoding.offset = segmentSizes[segmentIndex];
				segmentSizes[segmentIndex] += encoding.size;
			}
		}

		if (!errors.isEmpty()) {
			return null;
		}

		// Find the addresses of each segment in memory and in the file
		final long[] segmentAddresses = new long[headerSegmentCount+segments.size()];
		final long[] segmentFileAddresses = new long[headerSegmentCount+segments.size()];
		if (headerSegmentCount==1) {
			if (isWindows) {
				segmentAddresses[0] = isDLL ? IMAGE_BASE_DLL : IMAGE_BASE_EXE;
			}
			segmentFileAddresses[0] = 0;
		}
		long lastSegmentFileEndAddress = (headerSegmentCount!=0) ? headerEncoding.encoded.size() : 0;
		long largestSegmentFileEndAddress = lastSegmentFileEndAddress;
		for (int i=headerSegmentCount;i<headerSegmentCount+segments.size();++i) {
			Segment segment = segments.get(i-headerSegmentCount);
			if (segment.getMaxSize().get()!=Segment.NO_MAX_SIZE && segmentSizes[i]>segment.getMaxSize().get()) {
				synchronized (errors) {
					errors.add(new SyntaxError(null,0,0,"The segment \""+segment.getName()+"\" is "+segmentSizes[i]+" bytes, but its maximum size is "+segment.getMaxSize()+".  It is "+(segmentSizes[i]-segment.getMaxSize().get())+" bytes too large."));
				}
			}
			if (segment.getAddress().get()!=Segment.ANY_ADDRESS) {
				segmentAddresses[i] = segment.getAddress().get();
			}
			else if (i>0) {
				long alignment = segment.getAlignment().get();
				// Round up to next multiple of alignment from previous segment
				segmentAddresses[i] = (segmentAddresses[i-1]+segmentSizes[i-1]+alignment-1)&~(alignment-1);
			}
			else {
				// If the first segment has no address, just make it 0 for now
				// Absolute relocations will need to be kept
				segmentAddresses[i] = 0;
			}
			// Check for overlap, which would be problematic
			for (int j=0;j<i;++j) {
				if ((segmentAddresses[i]<segmentAddresses[j] && segmentAddresses[i]+segmentSizes[i]>segmentAddresses[j])
						|| (segmentAddresses[j]<segmentAddresses[i] && segmentAddresses[j]+segmentSizes[j]>segmentAddresses[i])
						|| (segmentAddresses[i]==segmentAddresses[j] && segmentSizes[i]>0 && segmentSizes[j]>0)) {
					synchronized (errors) {
						errors.add(new SyntaxError(null,0,0,"The segments "+segment.getName()+" ("+Long.toHexString(segmentAddresses[i])+"h-"+Long.toHexString(segmentAddresses[i]+segmentSizes[i])+"h) and "+((j==0 && headerSegmentCount==1) ? "the file header" : segments.get(j-headerSegmentCount).getName())+" ("+Long.toHexString(segmentAddresses[j])+"h-"+Long.toHexString(segmentAddresses[j]+segmentSizes[j])+"h) overlap in memory."));
					}
				}
			}

			if (segment.getType()==Segment.UNINIT_DATA) {
				segmentFileAddresses[i] = -1;
			}
			else if (segment.getFileAddress().get()!=Segment.ANY_ADDRESS) {
				segmentFileAddresses[i] = segment.getFileAddress().get();
			}
			else {
				long alignment = segment.getFileAlignment().get();
				// Round up to next multiple of alignment from previous segment
				segmentFileAddresses[i] = (lastSegmentFileEndAddress+alignment-1)&~(alignment-1);
			}
			if (segmentFileAddresses[i]>=0) {
				lastSegmentFileEndAddress = segmentFileAddresses[i]+segmentSizes[i];
				largestSegmentFileEndAddress = Math.max(largestSegmentFileEndAddress,lastSegmentFileEndAddress);
				// Check for overlap, which would be problematic
				for (int j=0;j<i;++j) {
					if (segmentFileAddresses[j]>=0) {
						if ((segmentFileAddresses[i]<segmentFileAddresses[j] && segmentFileAddresses[i]+segmentSizes[i]>segmentFileAddresses[j])
								|| (segmentFileAddresses[j]<segmentFileAddresses[i] && segmentFileAddresses[j]+segmentSizes[j]>segmentFileAddresses[i])
								|| (segmentFileAddresses[i]==segmentFileAddresses[j] && segmentSizes[i]>0 && segmentSizes[j]>0)) {
							synchronized (errors) {
								errors.add(new SyntaxError(null,0,0,"The segments "+segment.getName()+" ("+Long.toHexString(segmentFileAddresses[i])+"h-"+Long.toHexString(segmentFileAddresses[i]+segmentSizes[i])+"h) and "+((j==0 && headerSegmentCount==1) ? "the file header" : segments.get(j-headerSegmentCount).getName())+" ("+Long.toHexString(segmentFileAddresses[j])+"h-"+Long.toHexString(segmentFileAddresses[j]+segmentSizes[j])+"h) overlap in the output file."));
							}
						}
					}
				}
			}
		}

		if (!errors.isEmpty()) {
			return null;
		}

		// Handle extra adjustment to the file header before saving it.
		if (headerSegmentCount==1 && isWindows) {
			long executableLoadAddress = isDLL ? IMAGE_BASE_DLL : IMAGE_BASE_EXE;
			int fileAlignMask = FILE_ALIGNMENT-1;
			int sectionAlignMask = SECTION_ALIGNMENT-1;
			int codeSegmentIndex = 1+segments.indexOf(project.getDefaultSegment(Segment.CODE));
			int rdataSegmentIndex = 1+segments.indexOf(project.getDefaultSegment(Segment.READ_ONLY_DATA));
			int dataSegmentIndex = 1+segments.indexOf(project.getDefaultSegment(Segment.INIT_DATA));
			int udataSegmentIndex = 1+segments.indexOf(project.getDefaultSegment(Segment.UNINIT_DATA));
			int codeSize = (int)segmentSizes[codeSegmentIndex];
			int codeSizeF = (codeSize+fileAlignMask)&~fileAlignMask;
			int codeSizeS = (codeSize+sectionAlignMask)&~sectionAlignMask;
			int initDataSize = (int)segmentSizes[dataSegmentIndex];
			int uninitDataSize = (int)segmentSizes[udataSegmentIndex];
			int initDataSizeF = (initDataSize+fileAlignMask)&~fileAlignMask;
			int fullDataSizeF = (uninitDataSize+initDataSize+fileAlignMask)&~fileAlignMask;
			int fullDataSizeS = (fullDataSizeF+sectionAlignMask)&~sectionAlignMask;
			int headerSize = headerEncoding.encoded.size();
			int headerSizeS = (headerSize+sectionAlignMask)&~sectionAlignMask;
			int readOnlyDataSize = (int)segmentSizes[rdataSegmentIndex];
			int readOnlyDataSizeF = (readOnlyDataSize+fileAlignMask)&~fileAlignMask;
			int readOnlyDataSizeS = (readOnlyDataSize+sectionAlignMask)&~sectionAlignMask;
			int imageSizeS = headerSizeS+codeSizeS+fullDataSizeS+readOnlyDataSizeS;
			int codeRVA = (int)(segmentAddresses[codeSegmentIndex]-executableLoadAddress);
			int rdataRVA = (int)(segmentAddresses[rdataSegmentIndex]-executableLoadAddress);
			int dataRVA = (int)(segmentAddresses[dataSegmentIndex]-executableLoadAddress);

			// Set elements near beginning of PE header
			headerEncoding.encoded.setInt(OFFSET_PE_HEADER+4,codeSizeF);
			headerEncoding.encoded.setInt(OFFSET_PE_HEADER+8,fullDataSizeF);
			headerEncoding.encoded.setInt(OFFSET_PE_HEADER+20,codeRVA);
			if (!is64Bit) {
				headerEncoding.encoded.setInt(OFFSET_PE_HEADER+24,rdataRVA);
			}
			// Set executable memory image size
			headerEncoding.encoded.setInt(OFFSET_PE_HEADER+56,imageSizeS);
			// Set section table entries
			int offsetSectionTable = is64Bit ? OFFSET_SECTION_TABLE64 : OFFSET_SECTION_TABLE32;
			int offsetCodeEntry = offsetSectionTable + SECTION_INDEX_CODE*SIZEOF_SECTION_ENTRY;
			int offsetRDataEntry = offsetSectionTable + SECTION_INDEX_RDATA*SIZEOF_SECTION_ENTRY;
			int offsetDataEntry = offsetSectionTable + SECTION_INDEX_DATA*SIZEOF_SECTION_ENTRY;
			headerEncoding.encoded.setInt(offsetCodeEntry+8,codeSize);
			headerEncoding.encoded.setInt(offsetCodeEntry+12,codeRVA);
			headerEncoding.encoded.setInt(offsetCodeEntry+16,codeSizeF);
			headerEncoding.encoded.setInt(offsetCodeEntry+20,(int)segmentFileAddresses[codeSegmentIndex]);
			headerEncoding.encoded.setInt(offsetRDataEntry+8,readOnlyDataSize);
			headerEncoding.encoded.setInt(offsetRDataEntry+12,rdataRVA);
			headerEncoding.encoded.setInt(offsetRDataEntry+16,readOnlyDataSizeF);
			headerEncoding.encoded.setInt(offsetRDataEntry+20,(int)segmentFileAddresses[rdataSegmentIndex]);
			headerEncoding.encoded.setInt(offsetDataEntry+8,initDataSize+uninitDataSize);
			headerEncoding.encoded.setInt(offsetDataEntry+12,dataRVA);
			headerEncoding.encoded.setInt(offsetDataEntry+16,initDataSizeF);
			headerEncoding.encoded.setInt(offsetDataEntry+20,(int)segmentFileAddresses[dataSegmentIndex]);
			if (initDataSize==0) {
				// Mark it as not containing initialized data if it doesn't
				headerEncoding.encoded.setInt(offsetDataEntry+36,SECTION_FLAG_READABLE|SECTION_FLAG_WRITABLE);
			}
		}

		// Resolve all relocations and copy each final function and variable into its location in the file
		int endOfFile = isWindows ? ((int)largestSegmentFileEndAddress+FILE_ALIGNMENT-1)&~(FILE_ALIGNMENT-1) : (int)largestSegmentFileEndAddress;
		final byte[] finalEncoding = new byte[endOfFile];
		final EncodedData[] newEncodings2 = newEncodings;
		final HashMap<Long,Long> emptySymbolAddresses = new HashMap<Long, Long>(0);
		nextToEncode.set(0);
		for (int i=0;i<numProcessors;++i) {
			Thread thread = new Thread() {
				public void run() {
					try {
						int i;
						while ((i = nextToEncode.getAndIncrement())<newEncodings2.length) {
							EncodedData encoding = newEncodings2[i];
							// Only include things that aren't in uninitialized data segments and take up more than 0 bytes
							if (encoding!=null && encoding.encoded!=null) {
								int segmentIndex = encoding.segmentIndex+headerSegmentCount;
								int mode = (encoding.segmentIndex>=0) ? segments.get(encoding.segmentIndex).getnBits().get() : (is64Bit?64:32);
								// FIXME: Figure out how to support offsets >=2GB in Java, or use C\C++
								int fileOffset = (int)(encoding.offset+segmentFileAddresses[segmentIndex]);
								long memAddress = encoding.offset+segmentAddresses[segmentIndex];
								if (encoding.alignment!=CodeFileChunk.DEFAULT_ALIGNMENT_MARKER && (memAddress & (encoding.alignment-1))!=0) {
									synchronized (errors) {
										errors.add(new SyntaxError(encoding.code!=null ? encoding.code.getName() : null,0,0,"This has an alignment of "+encoding.alignment+", while it's segment ("+((encoding.segmentIndex>=0) ? segments.get(encoding.segmentIndex).getName() : "the file header")+") has an alignment of "+((encoding.segmentIndex>=0) ? segments.get(encoding.segmentIndex).getAlignment().get() : SECTION_ALIGNMENT)+" and it doesn't happen to work out to align this properly."));
									}
								}
								HashMap<Long,Long> symbolAddresses;
								if (encoding.symbolOffsets!=null) {
									symbolAddresses = new HashMap<Long, Long>(encoding.symbolOffsets.size());
									for (SymbolOffset s : encoding.symbolOffsets) {
										symbolAddresses.put(s.id,memAddress+s.offset);
									}
								}
								else {
									symbolAddresses = emptySymbolAddresses;
								}
								if (encoding.relocations!=null) {
									// Resolve Relocations here, without modifying encoding.relocations, because ILT and IAT share relocations!!!
									for (Relocation r : encoding.relocations) {
										Long address = null;
										Long addressA = symbolAddresses.get(r.symbolID);
										if (addressA==null && globalSymbolIndices.containsKey(r.symbolID)) {
											EncodedData otherEncoding = newEncodings2[globalSymbolIndices.get(r.symbolID)];
											addressA = otherEncoding.offset+segmentAddresses[otherEncoding.segmentIndex+headerSegmentCount];
										}
										switch (r.type) {
											case RELOC_NORMAL: {
												address = addressA;
												break;
											}
											case RELOC_DIFF: {
												Long addressB = symbolAddresses.get(r.negativeSymbolID);
												if (addressB==null && globalSymbolIndices.containsKey(r.negativeSymbolID)) {
													EncodedData otherEncoding = newEncodings2[globalSymbolIndices.get(r.negativeSymbolID)];
													addressB = otherEncoding.offset+segmentAddresses[otherEncoding.segmentIndex+headerSegmentCount];
												}
												if (addressA!=null && addressB!=null) {
													address = addressA - addressB;
												}
												break;
											}
											case RELOC_REL: {
												if (addressA==null) {
													System.err.println("Oh noes! a symbol (referenced at " + Long.toHexString(r.offset) + "h) that's not in symbolAddresses");
												}
												else {
//							System.out.println("Getting out id="+r.symbolID+" addr="+Long.toHexString(address));
//							System.out.println("Symbol found at " + Long.toHexString(address) + "h");
													// NOTE: The +4 or +2 is because these relocations are relative to just after them.
													address = addressA - (memAddress + r.offset + ((mode!=16) ? 4 : 2));
												}
												break;
											}
										}
										if (address!=null) {
//					System.out.println("Found address " + Long.toHexString(address) + "h for relocation at " + Long.toHexString(r.offset));
											if (mode!=16) {
												encoding.encoded.setInt((int)r.offset, (int)(address + encoding.encoded.getInt((int)r.offset)));
											}
											else {
												encoding.encoded.setShort((int)r.offset, (short)(address + encoding.encoded.getShort((int)r.offset)));
											}
										}
									}
								}
								if (encoding.encoded!=null) {
									encoding.encoded.toArray(finalEncoding,fileOffset);
								}
							}
						}
					}
					catch (Throwable t) {
						// If an exception occurs, save it, and finish off the rest of the threads
						exceptionRef[0] = t;
						nextToEncode.set(newEncodings2.length);
					}
					try {
						barrier.await();
					}
					catch (Exception e) {
						e.printStackTrace();
					}
				}
			};
			thread.start();
		}
		barrier.await();
		if (exceptionRef[0]!=null) {
			throw exceptionRef[0];
		}

		if (!errors.isEmpty()) {
			return null;
		}

		// Find the file name
		if (executableName==null || executableName.length()==0) {
			executableName = project.getFile().getParent()+File.separatorChar+project.getDirectory().getName();
			if (isWindows) {
				executableName += isDLL ? ".dll" : ".exe";
			}
			else {
				executableName += ".bin";
			}
		}

		// Finally, write the data to the file
		FileOutputStream file = new FileOutputStream(executableName);
		file.write(finalEncoding);
		file.close();
		return executableName;
	}

	protected abstract EncodedData encode(GlobalVariable variable,ArrayList<SyntaxError> errors);
	protected abstract EncodedData encode(Function function,HashMap<Long,Function> importSet,ArrayList<SyntaxError> errors);

	/**
	 * Resolves jumps in the given segment data.  All jumps to labels in the segment data are resolved as much as possible.
	 * If there are jumps to outside the segment data, they are assumed to be long, but cannot be resolved completely.
	 * NOTE: For an explanation of the algorithm, see <a href="http://arxiv.org/abs/0812.4973/">the paper on it</a>.
	 * @param encodedData the segment data to crawl for jumps
	 * @param mode the current CPU mode (16-bit, 32-bit, or 64-bit)
	 * @param errors the list of errors to be filled with any errors due to jump resolution.  NOTE: This must be synchronized-on before use, because multiple threads add to it.
	 * @return true if no RELOC_JMP_SHORT_ONLY that ended up being long, else false
	 */
	private static boolean resolveJumps(EncodedData encodedData,int mode,ArrayList<SyntaxError> errors) {
		if (encodedData.encoded==null || encodedData.relocations==null) {
			return true;
		}
		boolean isSuccessful = true;
		ArrayList<Relocation> jumpRelocations = new ArrayList<Relocation>();
		for (Relocation r : encodedData.relocations) {
			if (r.type==RELOC_JCC_SHORT || r.type==RELOC_JMP_SHORT || r.type==RELOC_JMP_SHORT_ONLY) {
				jumpRelocations.add(r);
			}
		}
//		System.out.println("Jump Relocations: "+jumpRelocations.size());
		if (!jumpRelocations.isEmpty()) {
			HashMap<Long,Long> symbolOffsets = new HashMap<Long,Long>(encodedData.symbolOffsets.size());
			for (SymbolOffset s : encodedData.symbolOffsets) {
				symbolOffsets.put(s.id,s.offset);
			}
			final byte longUnconditionalSizeDelta = (byte)((mode!=16) ? 3 : 1);
			final byte longConditionalSizeDelta = (byte)((mode!=16) ? 4 : 2);
			LinkedList<Integer> longQueue = new LinkedList<Integer>();
			// Determine the current jump distances
			long[] jumpShortDistances = new long[jumpRelocations.size()];
			byte[] jumpSizeDeltas = new byte[jumpRelocations.size()];
			int totalSize = encodedData.encoded.size();
			// Find the jumps that are long even if all other jumps could be encoded as short.
			for (int i=0;i<jumpRelocations.size();++i) {
				Relocation r = jumpRelocations.get(i);
				jumpSizeDeltas[i] = (r.type==RELOC_JCC_SHORT) ? longConditionalSizeDelta : longUnconditionalSizeDelta;
				Long symbolOffset = symbolOffsets.get(r.symbolID);
				if (symbolOffset!=null) {
					// NOTE: The +2 is the length of a short jump, because jumps are relative to the next instruction.
					// NOTE: The -1 is because the relocation is 1 byte after the start of the jump
					jumpShortDistances[i] = r.offset+2-1-symbolOffset;
					// If guaranteed to be a long jump
					if (jumpShortDistances[i]<-128 || jumpShortDistances[i]>127) {
						if (r.type==RELOC_JMP_SHORT_ONLY) {
							isSuccessful = false;
							addShortJumpError(r,errors);

						}
						// Mark it as a long jump and add it to the queue
						r.type = RELOC_REL;
						longQueue.addLast(i);
						totalSize += jumpSizeDeltas[i];
					}
					// If guaranteed to be a short jump (i.e. still short even if all instructions it jumps over but itself turn out to be conditional long jumps)
					else if (jumpShortDistances[i]>=-44 && jumpShortDistances[i]<=42) {
						// TODO: Figure out a good way of removing these jumps from consideration without wasting too much time.
					}
				}
				// If jumping to otuside the segment data
				else {
					if (r.type==RELOC_JMP_SHORT_ONLY) {
						isSuccessful = false;
						addShortJumpError(r, errors);
					}
					// Force the jump to be long, while clearly marking it as unresolved
					jumpShortDistances[i] = Long.MAX_VALUE;
					r.type = RELOC_REL;
					longQueue.addLast(i);
					totalSize += jumpSizeDeltas[i];
				}
			}
			long[] jumpDistances = new long[jumpShortDistances.length];
			System.arraycopy(jumpShortDistances,0,jumpDistances,0,jumpShortDistances.length);
			// Go through and determine all long jumps.
			while (!longQueue.isEmpty()) {
				int longJumpIndex = longQueue.removeFirst();
				long longJumpOffset = jumpRelocations.get(longJumpIndex).offset;
				int sizeDelta = jumpSizeDeltas[longJumpIndex];
				// For each jump up to 128 bytes back
				for (int i=longJumpIndex-1; i>=0 && (longJumpOffset-jumpRelocations.get(i).offset)<128; --i) {
					Relocation r = jumpRelocations.get(i);
					// If this jump is still a short, forward jump that jumps over the new long jump
					// NOTE: The -1 from above isn't needed here since both sides of the ">" are relocation offsets.
					if (r.type!=RELOC_REL && jumpShortDistances[i]>0 && (r.offset+2+jumpShortDistances[i])>longJumpOffset) {
						jumpDistances[i] += sizeDelta;
						if (jumpDistances[i]>=128) {
							if (r.type==RELOC_JMP_SHORT_ONLY) {
								isSuccessful = false;
								addShortJumpError(r, errors);
							}
							r.type = RELOC_REL;
							longQueue.addLast(i);
							totalSize += jumpSizeDeltas[i];
						}
					}
				}
				// For each jump up to 128 bytes forward
				for (int i=longJumpIndex+1; i<jumpRelocations.size() && (jumpRelocations.get(i).offset-longJumpOffset)<=128; ++i) {
					Relocation r = jumpRelocations.get(i);
					// If this jump is still a short, backward jump that jumps over the new long jump
					// NOTE: The -1 from above isn't needed here since both sides of the "<=" are relocation offsets.
					if (r.type!=RELOC_REL && jumpShortDistances[i]<0 && (r.offset+2+jumpShortDistances[i])<=longJumpOffset) {
						jumpDistances[i] -= sizeDelta;
						if (jumpDistances[i]<-128) {
							if (r.type==RELOC_JMP_SHORT_ONLY) {
								isSuccessful = false;
								addShortJumpError(r, errors);
							}
							r.type = RELOC_REL;
							longQueue.addLast(i);
							totalSize += jumpSizeDeltas[i];
						}
					}
				}
			}
			ByteList encoded = new ByteList(totalSize);
			int start = 0;
			int symbolIndex = 0;
			int relocIndex = 0;
			int increase = 0;
			// Take the existing encoding and create a new encoding with space for the long jumps, and update the relocation offsets and symbol offsets.
			for (int i=0;i<jumpRelocations.size();++i) {
				Relocation r = jumpRelocations.get(i);
				if (r.type==RELOC_REL) {
					// NOTE: The -1 is because the relocation is 1 byte after the start of the jump
					encoded.addRange(encodedData.encoded,start,(int)(r.offset-1));
					if (jumpSizeDeltas[i]==longConditionalSizeDelta) {
						// Insert 0Fh and add 10h to turn 7x (jcc short) into 0F 8x (jcc near)
						encoded.addShort((short)((encodedData.encoded.get((int)r.offset-1)<<8) + 0x100F));
					}
					else {
						// Subtracting 2 turns EBh (jmp short) into E9h (jmp near)
						encoded.add((byte)(encodedData.encoded.get((int)r.offset-1)-2));
					}
					if (mode!=16) {
						encoded.addInt(0);
					}
					else {
						encoded.addShort((short)0);
					}
					// NOTE: The -1 is because the relocation is 1 byte after the start of the jump
					while (symbolIndex<encodedData.symbolOffsets.size() && encodedData.symbolOffsets.get(symbolIndex).offset<=r.offset-1) {
						long offset = encodedData.symbolOffsets.get(symbolIndex).offset+increase;
						encodedData.symbolOffsets.get(symbolIndex).offset = offset;
						symbolOffsets.put(encodedData.symbolOffsets.get(symbolIndex).id,offset);
						++symbolIndex;
					}
					while (encodedData.relocations.get(relocIndex)!=r) {
						encodedData.relocations.get(relocIndex).offset += increase;
						++relocIndex;
					}
					// NOTE: The +2 is the length of a short jump, because jumps are relative to the next instruction.
					// NOTE: The -1 is because the relocation is 1 byte after the start of the jump
					start = (int)(r.offset+2-1);
					// Add 1 to the offset of a long conditional relocation, since it one byte has been added before it.
					r.offset += increase + ((jumpSizeDeltas[i]==longConditionalSizeDelta) ? 1 : 0);
					++relocIndex;
					increase += jumpSizeDeltas[i];
				}
			}
			// Put in the last of the encoding (after all long jumps)
			encoded.addRange(encodedData.encoded,start,encodedData.encoded.size());
			// Update the offsets of symbols after all of the long jumps
			while (symbolIndex<symbolOffsets.size()) {
				long offset = encodedData.symbolOffsets.get(symbolIndex).offset+increase;
				encodedData.symbolOffsets.get(symbolIndex).offset = offset;
				symbolOffsets.put(encodedData.symbolOffsets.get(symbolIndex).id,offset);
				++symbolIndex;
			}
			while (relocIndex<encodedData.relocations.size()) {
				encodedData.relocations.get(relocIndex).offset += increase;
				++relocIndex;
			}
			// Put in the distances of the jumps that can be resolved.
			for (Relocation r : jumpRelocations) {
				Long symbolOffset = symbolOffsets.get(r.symbolID);
				if (symbolOffset!=null) {
					if (r.type==RELOC_REL) {
						int distance = (int)(symbolOffset - (r.offset + ((mode!=16) ? 4 : 2)));
						if (mode!=16) {
							encoded.setInt((int)r.offset, distance);
						}
						else {
							encoded.setShort((int)r.offset, (short)distance);
						}
					}
					else {
						int distance = (int)(symbolOffset - (r.offset + 1));
						encoded.set((int)r.offset, (byte)distance);
					}
					// Mark the relocation for removal.  There will be no short jumps left, so this works to mark it.
					r.type = RELOC_JMP_SHORT;
				}
			}
			// Remove jump relocations that were resolved (they are all marked by type==RELOC_JMP_SHORT).
			int destIndex = 0;
			for (int i=0;i<encodedData.relocations.size();++i) {
				Relocation r = encodedData.relocations.get(i);
				if (r.type!=RELOC_JMP_SHORT) {
					encodedData.relocations.set(destIndex++,r);
				}
			}
			for (int i=encodedData.relocations.size()-1;i>=destIndex;--i) {
				encodedData.relocations.remove(i);
			}
			encodedData.encoded = encoded;
		}
		return isSuccessful;
	}

	private static void addShortJumpError(Relocation r, ArrayList<SyntaxError> errors) {
		// Find the errant line label or function used as the jump target.
		Object[] refs = r.line.getReferences();
		int index = -1;
		for (int j=0;j<r.line.getNumReferences();++j) {
			if (refs[j]!=null && refs[j] instanceof Symbol && ((Symbol)refs[j]).getID()==r.symbolID) {
				index = j;
				break;
			}
		}
		SyntaxError error = new SyntaxError(r.line,(index>=0)?r.line.getTokenColumn(index):0,(index>=0)?r.line.getTokenColumn(index+1):r.line.length(),"This instruction only allows jumps of up to 128 bytes backward or 127 bytes forward, and it's currently trying to jump farther.\nOther jump instructions don't have this restriction.");
		r.line.addError(error);
		synchronized (errors) {
			errors.add(error);
		}
	}

	public static EncodedData createPEHeader(Function entryPoint,boolean is64Bit,boolean isDLL) throws IOException {
		// TODO: Support relocatable executables.
		boolean isRelocatable = false;
		EncodedData encoding = new EncodedData();
		int headerSize = (is64Bit?HEADER_SIZE64:HEADER_SIZE32);
		encoding.size = headerSize;
		encoding.encoded = new ByteList(headerSize);
		encoding.relocations = new ArrayList<Relocation>();
		// Write the DOS header with only the initial signature "MZ" and the relocation offset of 0x40 to indicate non-DOS program.
		// Everything else is 0, because no DOS stub is included.
		encoding.encoded.addShort((short)('M'+('Z'<<8)));
		encoding.encoded.addInt(0x30090);
		encoding.encoded.addShort((short)0);
		encoding.encoded.addInt(0x4);
		encoding.encoded.addInt(0xFFFF);
		encoding.encoded.addInt(SIZEOF_DOS_STUB);
		encoding.encoded.addInt(0x0);
		encoding.encoded.addShort((short)0x40);						// This is needed to indicate that there is a Windows program
		encoding.encoded.addShort((short)0);
		encoding.encoded.addInt(0);
		encoding.encoded.addZeros(0x3C-0x20);
		encoding.encoded.addInt(SIZEOF_DOS_STUB);					// This points to the Windows program
		encoding.encoded.addZeros(SIZEOF_DOS_STUB-0x40);
		// Write COFF header
		encoding.encoded.addInt('P'+('E'<<8));						// Signature
		encoding.encoded.addShort((short)(is64Bit?0x8664:0x14C));	// CPU Type is x86-64 or x86
		encoding.encoded.addShort((short)NUM_SECTIONS);				// Number of section table entries
		encoding.encoded.addInt((int)(new Date().getTime()/1000));
		encoding.encoded.addLong(0);
		encoding.encoded.addShort(is64Bit?SIZEOF_PE32PLUS_HEADER:SIZEOF_PE32_HEADER);
		// Have non-libraries be non-relocatable, to avoid having to do relocation entries
		encoding.encoded.addShort((short)(HEADER_FLAG_EXECUTABLE|HEADER_FLAG_LARGE_ADDR|(isDLL?HEADER_FLAG_LIBRARY:0)|(isRelocatable?0:HEADER_FLAG_FIXED)));
		// Write PE32+ Header
		encoding.encoded.addShort(is64Bit?PE32PLUS_MARKER:PE32_MARKER);
		encoding.encoded.addShort((short)0);						// Linker major and minor version

		// **  = value set automatically later on
		// *** = value set explicity later on
		encoding.encoded.addInt(0);	// *** Code section size in file
		encoding.encoded.addInt(0);	// *** Initialized data size in file, including uninitialized part (as paradoxical as it is to include a part that isn't really in the file)
		encoding.encoded.addInt(0);	// Would be uninitialized data size, if it wasn't being added to the end of the init data section
		long executableLoadAddress = isDLL ? IMAGE_BASE_DLL : IMAGE_BASE_EXE;
		if (entryPoint!=null) {
			int entryPointRefOffset = encoding.encoded.size();
			encoding.encoded.addInt((int)-executableLoadAddress);	// **  entry point RVA
			encoding.relocations.add(new Relocation(null,RELOC_NORMAL,entryPointRefOffset,entryPoint.getID()));
		}
		else {
			encoding.encoded.addInt(0);								// entry point RVA of 0, since no entry point
		}
		encoding.encoded.addInt(0);									// ***  code segment start RVA
		if (!is64Bit) {
			encoding.encoded.addInt(0);								// ***  read-only data segment start RVA
			encoding.encoded.addInt((int)executableLoadAddress);
		}
		else {
			encoding.encoded.addLong(executableLoadAddress);
		}
		encoding.encoded.addInt(SECTION_ALIGNMENT);
		encoding.encoded.addInt(FILE_ALIGNMENT);
		encoding.encoded.addInt(4);									// OS major and minor version (4 means 4.0)
		encoding.encoded.addInt(4);									// Image major and minor version
		encoding.encoded.addInt(4);									// Subsystem major and minor version
		encoding.encoded.addInt(0);									// Reserved
		encoding.encoded.addInt(0);									// *** Executable image size as will appear in memory
		int fileAlignMask = FILE_ALIGNMENT-1;
		int headerSizeF = (headerSize+fileAlignMask)&~fileAlignMask;
		encoding.encoded.addInt(headerSizeF);	// Header size in the file
		// TODO: Figure out how to calculate the checksum and store it next, but it must be calculated later on
		encoding.encoded.addInt(0);
		encoding.encoded.addInt(0x00000002);				// Subsystem and DLL Characteristics
		if (is64Bit) {
			encoding.encoded.addLong(0x00100000);			// Reserved stack size (1MB)
			encoding.encoded.addLong(0x00001000);			// Committed stack size (4KB)
			encoding.encoded.addLong(0x00100000);			// Reserved heap size (1MB)
			encoding.encoded.addLong(0x00001000);			// Committed heap size (4KB)
		}
		else {
			encoding.encoded.addInt(0x00100000);			// Reserved stack size (1MB)
			encoding.encoded.addInt(0x00001000);			// Committed stack size (4KB)
			encoding.encoded.addInt(0x00100000);			// Reserved heap size (1MB)
			encoding.encoded.addInt(0x00001000);			// Committed heap size (4KB)
		}
		encoding.encoded.addInt(0);
		encoding.encoded.addInt(NUM_RVA_TABLE_ENTRIES);

		// Write RVAs (Relative Virtual Addresses) & sizes to finish off PE32/PE32+ header
		// *** RVA/Size of exports, RVA/Size of IDT, RVA/Size of relocations, RVA/Size of IAT
		encoding.encoded.addZeros(NUM_RVA_TABLE_ENTRIES*8);

		// Write out the section table
		encoding.encoded.addString(".text");
		encoding.encoded.addZeros(3);	// padding name to 8 bytes
		encoding.encoded.addInt(0);							// *** Size of code segment, to the byte
		encoding.encoded.addInt(0);							// *** RVA of code segment if present
		encoding.encoded.addInt(0);							// *** Size of code section in the file
		encoding.encoded.addInt(0);							// *** Offset of code section in the file
		encoding.encoded.addZeros(12);
		encoding.encoded.addInt(SECTION_FLAG_READABLE|SECTION_FLAG_EXECUTABLE|SECTION_FLAG_CONTAINS_CODE);

		encoding.encoded.addString(".rdata");
		encoding.encoded.addZeros(2);	// padding name to 8 bytes
		encoding.encoded.addInt(0);							// *** Size of rdata segment, to the byte
		encoding.encoded.addInt(0);							// *** RVA of rdata segment if present
		encoding.encoded.addInt(0);							// *** Size of rdata section in the file
		encoding.encoded.addInt(0);							// *** Offset of rdata section in the file
		encoding.encoded.addZeros(12);
		encoding.encoded.addInt(SECTION_FLAG_READABLE|SECTION_FLAG_CONTAINS_INIT_DATA);

		encoding.encoded.addString(".data");
		encoding.encoded.addZeros(3);	// padding name to 8 bytes
		encoding.encoded.addInt(0);							// *** Size of data segment, to the byte, including uninitialized data
		encoding.encoded.addInt(0);							// *** RVA of data segment
		encoding.encoded.addInt(0);							// *** Size of initialized data section in the file
		encoding.encoded.addInt(0);							// *** Offset of initialized data section in the file
		encoding.encoded.addZeros(12);
		encoding.encoded.addInt(SECTION_FLAG_READABLE|SECTION_FLAG_WRITABLE|SECTION_FLAG_CONTAINS_INIT_DATA);
		encoding.segmentIndex = -1;
		return encoding;
	}

	public static class EncodedData {
		public CodeFileChunk			code;
		public int						segmentIndex;
		public int						alignment;
		public long						offset;
		public ByteList					encoded;
		public long						size;
		public ArrayList<Relocation>	relocations;
		public ArrayList<SymbolOffset>	symbolOffsets;

		public EncodedData() {
			code = null;
			alignment = CodeFileChunk.DEFAULT_ALIGNMENT_MARKER;
			offset = -1;
			encoded = null;
			relocations = null;
			symbolOffsets = null;
			size = 0;
		}
	}

	public static class Relocation implements Copiable<Relocation> {
		public Line line;
		public int	type;
		public long	offset;
		public long	symbolID;
		public long	negativeSymbolID;

		public Relocation(Line line,Symbol symbol) {
			this(line,RELOC_NORMAL,-1,symbol.getID(),-1);
		}

		public Relocation(Line line,int type, long offset, long symbolID) {
			this(line,type,offset,symbolID,-1);
		}

		public Relocation(Line line,int type, long offset, long symbolID, long negativeSymbolID) {
			this.line = line;
			this.type = type;
			this.offset = offset;
			this.symbolID = symbolID;
			this.negativeSymbolID = negativeSymbolID;
		}

		public boolean equals(Object o) {
			if (!(o instanceof Relocation)) {
				return false;
			}
			Relocation that = (Relocation)o;
			return type==that.type && offset==that.offset && symbolID==that.symbolID && negativeSymbolID==that.negativeSymbolID;
		}

		public Relocation copy() {
			return new Relocation(line,type,offset,symbolID,negativeSymbolID);
		}
	}

	public static class SymbolOffset {
		public long id;
		public long offset;

		public SymbolOffset(long id, long offset) {
			this.id = id;
			this.offset = offset;
		}
	}
}